Repository navigation
mail 2/4: agent mail service group — Postfix/Dovecot/OpenDKIM - #503
Merged
Merged
Conversation
Split 2/4 of #495. Adds the agent-side mail service: - Service groups via AGENT_SERVICES - Mail service group managing Postfix, Dovecot, and OpenDKIM - Config templates for mail stack (SNI, SQL maps, DKIM tables) - Only stops mail units this agent started Merge after the manager backend PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved flaws permit cross-domain DKIM impersonation and plaintext relay credential exposure, while invalid PostgreSQL settings cause Dovecot configuration validation to fail.
14 open issues
PostgreSQL Dovecot parameters use an invalid config structure · New Unauthorized From domains can obtain aligned DKIM signatures · New Credentialed relay permits plaintext SASL authentication · New Partial startup leaves mail management state unset · New Service group changes do not invalidate the saved ETag · New 304 responses skip mailbox lifecycle reconciliation · New Custom VMAIL_DIR is ignored by Dovecot mailbox homes · New Dovecot SQL configuration omits the database port · New Certificate renewals do not trigger SNI map regeneration · New Inactive mail units remain enabled at boot · New Stop failures are incorrectly treated as missing units · New HTML-escaped password breaks Dovecot database authentication · New Chrooted smtpd cannot access database socket maps · New HTML-escaped password breaks Postfix database lookups · New
What changed in this PR
Adds the agent-side mail stack as the second part of the email feature, building on the manager backend in #502 alongside existing nginx and dnsmasq management.
Changes:
- Adds opt-in service groups, dependency reporting, and persisted mail lifecycle tracking.
- Introduces Postfix, Dovecot, OpenDKIM, and mail-helper configuration, provisioning, and mailbox retention handling.
- Extends configuration application with ownership reconciliation and post-write commands, plus tests, packaging suggestions, and documentation.
| File | Description |
|---|---|
| mie-opensource-landing/docs/developers/agent.md | Documents service groups, mail settings, and apply behavior. |
| agent/test/mail-template.test.js | Tests rendered mail configuration directives. |
| agent/test/config.test.js | Tests service-group parsing and validation. |
| agent/templates/mail/postfix-vdomains.ejs | Lists domains accepting inbound mail. |
| agent/templates/mail/postfix-sql-senders.cf.ejs | Configures sender-ownership lookups. |
| agent/templates/mail/postfix-sql-mailboxes.cf.ejs | Configures recipient-existence lookups. |
| agent/templates/mail/postfix-sni-map.ejs | Maps domains to TLS key and certificate files. |
| agent/templates/mail/postfix-master.cf.ejs | Defines SMTP listeners and submission policies. |
| agent/templates/mail/postfix-main.cf.ejs | Configures delivery, TLS, authentication, and milters. |
| agent/templates/mail/opendkim.conf.ejs | Configures DKIM signing and socket access. |
| agent/templates/mail/opendkim-signingtable.ejs | Maps sender domains to signing keys. |
| agent/templates/mail/opendkim-keytable.ejs | Maps DKIM selectors to private-key files. |
| agent/templates/mail/dovecot.conf.ejs | Configures IMAP, SQL authentication, LMTP, and quotas. |
| agent/src/types.ts | Defines mail snapshots and service-reporting fields. |
| agent/src/system.ts | Adds binary checks, ownership helpers, and unit lifecycle operations. |
| agent/src/state.ts | Persists mail management responsibility. |
| agent/src/mail.ts | Implements mail rendering, provisioning, lifecycle, and mailbox cleanup. |
| agent/src/index.ts | Integrates enabled groups and mail housekeeping into check-ins. |
| agent/src/config.ts | Parses and validates AGENT_SERVICES. |
| agent/src/apply.ts | Adds service groups, file ownership, and post-write processing. |
| agent/.fpm | Suggests optional mail-stack packages. |
Review effort: Balanced
Comment on lines
+32
to
+40
| <%= db.dovecotDriver %> <%= db.dovecotHost %> { | ||
| <%_ if (db.dovecotParams.dbname) { _%> | ||
| dbname = <%= db.dovecotParams.dbname %> | ||
| <%_ } _%> | ||
| user = <%= db.dovecotUser %> | ||
| <%_ if (db.dovecotPassword) { _%> | ||
| password = <%= db.dovecotPassword %> | ||
| <%_ } _%> | ||
| } |
| @@ -0,0 +1,3 @@ | |||
| <%_ mail.dkim.forEach((k) => { _%> | |||
| *@<%= k.domain %> <%= k.selector %>._domainkey.<%= k.domain %> | |||
| tls_server_sni_maps = hash:/etc/postfix/sni_map | ||
| smtpd_tls_chain_files = <%= certs.fallbackKey %>, <%= certs.fallbackCert %> | ||
| smtpd_tls_security_level = may | ||
| smtp_tls_security_level = may |
Comment on lines
+73
to
+75
| const mailManaged = await syncMailUnits(config, state.mailManaged); | ||
| if (mailManaged !== state.mailManaged) { | ||
| state.mailManaged = mailManaged; |
| if (cfg.services.includes('mail') && result.config.mail) { | ||
| ensureMailProvisioned(cfg); | ||
| } | ||
| for (const svc of enabledServices(cfg)) { |
Comment on lines
+324
to
+327
| if (states.some((state) => state === 'active' || state === 'activating')) { | ||
| log.info('mail: service group disabled but units are running; stopping them'); | ||
| await stopAndDisableServices(MAIL_UNITS); | ||
| } |
| export async function stopAndDisableServices(units: string[]): Promise<void> { | ||
| const mgr = await getManager(); | ||
| for (const unit of units) { | ||
| await runJob('StopUnit', unit).catch(() => undefined); |
| <%_ } _%> | ||
| user = <%= db.dovecotUser %> | ||
| <%_ if (db.dovecotPassword) { _%> | ||
| password = <%= db.dovecotPassword %> |
Comment on lines
+37
to
+43
| virtual_mailbox_maps = <%= db.prefix %>:/etc/postfix/sql/mailboxes.cf | ||
| virtual_transport = lmtp:unix:private/dovecot-lmtp | ||
| smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination | ||
| smtpd_recipient_restrictions = reject_unauth_destination, check_policy_service unix:private/quota-status | ||
|
|
||
| # Sender ownership: submission enforces sender == SASL login (live from SQL). | ||
| smtpd_sender_login_maps = <%= db.prefix %>:/etc/postfix/sql/senders.cf |
| hosts = <%= db.hosts %> | ||
| user = <%= db.user %> | ||
| <%_ if (db.password) { _%> | ||
| password = <%= db.password %> |
runleveldev
approved these changes
Oct 29, 2026
runleveldev
left a comment
Collaborator
There was a problem hiding this comment.
I pretty extensively reviewed this section in the original PR, so no further comments.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Part 2 of 4 splitting #495 (feature issue: #67).
Adds the agent-side mail service:
AGENT_SERVICESagent.mddocs updatesMerge order: after #502 (manager backend).